#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <psp2/kernel/clib.h>

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_mixer.h>
#include <SDL2/SDL_ttf.h>
#include <smpeg2/smpeg.h>

#include "dynlib.h"
#include "so_util.h"
#include "jni.h"
#include "utils.h"
#include "patch/patch.h"

extern void *__cxa_atexit;
extern void *__cxa_finalize;
extern void *__cxa_allocate_exception;
extern void *__cxa_free_exception;
extern void *__cxa_throw;
extern void *__cxa_begin_catch;
extern void *__aeabi_memclr;
extern void *__aeabi_memcpy;
extern void *__aeabi_memmove;
extern void *__aeabi_memset;
extern void *__aeabi_memclr8;
extern void *__aeabi_memcpy8;
extern void *__aeabi_memset4;
extern void *__aeabi_memcpy4;
extern void *__aeabi_memclr4;
extern void *__aeabi_memmove4;
extern void *__stack_chk_fail;
extern void *__stack_chk_guard;
// extern void *__memcpy_chk;
// extern void *__vsprintf_chk;
// extern void *__strcpy_chk;
// extern void *__strlen_chk;
// extern void *__strcat_chk;
// extern void *__register_atfork;
extern void *__gxx_personality_v0;
// extern void *__open_2;
// extern void *__sF;

extern void *_ZdlPv;
extern void *_Znwj;
extern void *_ZdaPv;
extern void *_Znaj;
extern void *_ZNSt11logic_errorC2EPKc;
extern void *_ZNSt12length_errorD1Ev;
extern void *_ZTISt12length_error;
extern void *_ZTVSt12length_error;
extern void *_ZSt9terminatev;

static int __stack_chk_guard_fake = 0x42424242;
static FILE __sF_fake[0x100][3];

extern void *__memcpy_chk(void *dstpp, const void *srcpp, size_t len, size_t dstlen);
extern int __vsprintf_chk(char *s, int flag, size_t slen, const char *format, va_list ap);
extern char *__strcpy_chk(char *dest, const char *src, size_t destlen);
extern char *__strcat_chk(char *s, const char *append, size_t slen);

size_t __strlen_chk_fake(const char *s, size_t a2)
{
	return strlen(s);
}

void *mmap_fake(void *addr, size_t length, int prot, int flags, int fd, off_t offs)
{
	void *data = malloc(length);
	if (!data)
	{
		debugPrintf("mmap: failed\n");
		return (void *)-1;
	}

	void *buf = data;
	size_t nremaining = length;
	size_t nmemb = TRANSFER_SIZE;

	lseek(fd, offs, SEEK_SET);

	while (nremaining > 0)
	{
		if (nmemb > nremaining)
			nmemb = nremaining;

		int nread = read(fd, buf, nmemb);
		if (nread < 0)
		{
			free(data);
			debugPrintf("mmap: failed\n");
			return (void *)-1;
		}
		if (nread == 0)
			break;

		buf += nread;
		nremaining -= nread;
	}

	debugPrintf("mmap: size: %lu, fd: %d, offs: %d; return: %p\n", length, fd, offs, data);

	return data;
}

int munmap_fake(void *addr, size_t length)
{
	debugPrintf("munmap: addr: %p\n", addr);
	free(addr);
	return 0;
}

SoDefaultDynlib dynlib_functions[] = {
	// __
	{"__cxa_atexit", (uintptr_t)&__cxa_atexit},
	{"__cxa_finalize", (uintptr_t)&__cxa_finalize},
	{"__cxa_allocate_exception", (uintptr_t)&__cxa_allocate_exception},
	{"__cxa_free_exception", (uintptr_t)&__cxa_free_exception},
	{"__cxa_throw", (uintptr_t)&__cxa_throw},
	{"__cxa_begin_catch", (uintptr_t)&__cxa_begin_catch},
	{"__aeabi_memclr", (uintptr_t)&__aeabi_memclr},
	{"__aeabi_memcpy", (uintptr_t)&__aeabi_memcpy},
	{"__aeabi_memmove", (uintptr_t)&__aeabi_memmove},
	{"__aeabi_memset", (uintptr_t)&__aeabi_memset},
	{"__aeabi_memclr8", (uintptr_t)&__aeabi_memclr8},
	{"__aeabi_memcpy8", (uintptr_t)&__aeabi_memcpy8},
	{"__aeabi_memset4", (uintptr_t)&__aeabi_memset4},
	{"__aeabi_memcpy4", (uintptr_t)&__aeabi_memcpy4},
	{"__aeabi_memclr4", (uintptr_t)&__aeabi_memclr4},
	{"__aeabi_memmove4", (uintptr_t)&__aeabi_memmove4},
	{"__stack_chk_fail", (uintptr_t)&__stack_chk_fail},
	{"__stack_chk_guard", (uintptr_t)&__stack_chk_guard_fake},
	{"__memcpy_chk", (uintptr_t)&__memcpy_chk},
	{"__vsprintf_chk", (uintptr_t)&__vsprintf_chk},
	{"__strcpy_chk", (uintptr_t)&__strcpy_chk},
	{"__strlen_chk", (uintptr_t)&__strlen_chk_fake},
	{"__strcat_chk", (uintptr_t)&__strcat_chk},
	{"__register_atfork", (uintptr_t)&ret0},
	{"__gxx_personality_v0", (uintptr_t)&__gxx_personality_v0},
	{"__android_log_print", (uintptr_t)&__android_log_print},
	{"__android_log_vprint", (uintptr_t)&__android_log_vprint},
	{"__open_2", (uintptr_t)&open},
	{"__sF", (uintptr_t)&__sF_fake},

	// _Z
	{"_ZdlPv", (uintptr_t)&_ZdlPv},
	{"_Znwj", (uintptr_t)&_Znwj},
	{"_ZdaPv", (uintptr_t)&_ZdaPv},
	{"_Znaj", (uintptr_t)&_Znaj},
	{"_ZNSt11logic_errorC2EPKc", (uintptr_t)&_ZNSt11logic_errorC2EPKc},
	{"_ZNSt12length_errorD1Ev", (uintptr_t)&_ZNSt12length_errorD1Ev},
	{"_ZTISt12length_error", (uintptr_t)&_ZTISt12length_error},
	{"_ZTVSt12length_error", (uintptr_t)&_ZTVSt12length_error},
	{"_ZSt9terminatev", (uintptr_t)&_ZSt9terminatev},

	// C
	{"memcmp", (uintptr_t)&memcmp},
	{"exit", (uintptr_t)&exit},
	{"floorf", (uintptr_t)&floorf},
	{"printf", (uintptr_t)&printf},
	{"sincosf", (uintptr_t)&ret0},
	{"realloc", (uintptr_t)&realloc},
	{"free", (uintptr_t)&free},
	{"malloc", (uintptr_t)&malloc},
	{"strlen", (uintptr_t)&strlen},
	{"calloc", (uintptr_t)&calloc},
	{"strcpy", (uintptr_t)&strcpy},
	{"rand", (uintptr_t)&rand},
	{"srand", (uintptr_t)&srand},
	{"tan", (uintptr_t)&tan},
	{"time", (uintptr_t)&time},
	{"fprintf", (uintptr_t)&fprintf},
	{"stderr", (uintptr_t)&ret0},
	{"strcmp", (uintptr_t)&strcmp},
	{"putchar", (uintptr_t)&putchar},
	{"puts", (uintptr_t)&puts},
	{"ceilf", (uintptr_t)&ceilf},
	{"sin", (uintptr_t)&sin},
	{"strcat", (uintptr_t)&strcat},
	{"atoi", (uintptr_t)&atoi},
	{"difftime", (uintptr_t)&difftime},
	{"atan2", (uintptr_t)&atan2},
	{"fclose", (uintptr_t)&fclose},
	{"fopen", (uintptr_t)&fopen},
	{"strstr", (uintptr_t)&strstr},
	{"lseek", (uintptr_t)&lseek},
	{"mmap", (uintptr_t)&mmap_fake},
	{"truncate", (uintptr_t)&truncate},
	{"fread", (uintptr_t)&fread},
	{"fseek", (uintptr_t)&fseek},
	{"ftell", (uintptr_t)&ftell},
	{"fwrite", (uintptr_t)&fwrite},
	{"abort", (uintptr_t)&abort},
	{"fflush", (uintptr_t)&fflush},
	{"dl_unwind_find_exidx", (uintptr_t)&ret0}, // dl_unwind_find_exidx
	{"raise", (uintptr_t)&raise},
	{"localtime", (uintptr_t)&localtime},

	// SDL2
	{"Android_JNI_FileClose", (uintptr_t)&Android_JNI_FileClose},
	{"Android_JNI_FileRead", (uintptr_t)&Android_JNI_FileRead},
	{"Android_JNI_FileSeek", (uintptr_t)&Android_JNI_FileSeek},
	{"Android_JNI_FileSize", (uintptr_t)&Android_JNI_FileSize},
	{"Android_JNI_GetEnv", (uintptr_t)&Android_JNI_GetEnv},

	{"SDL_CreateRGBSurfaceFrom", (uintptr_t)&SDL_CreateRGBSurfaceFrom},
	{"SDL_FillRect", (uintptr_t)&SDL_FillRect},
	{"SDL_GetWindowFlags", (uintptr_t)&SDL_GetWindowFlags},
	{"SDL_SetColorKey", (uintptr_t)&SDL_SetColorKey},
	{"SDL_UpperBlit", (uintptr_t)&SDL_UpperBlit_soloader},
	{"SDL_Log", (uintptr_t)&ret0},
	{"SDL_LockSurface", (uintptr_t)&SDL_LockSurface},
	{"SDL_UnlockSurface", (uintptr_t)&SDL_UnlockSurface},
	{"SDL_FreeSurface", (uintptr_t)&SDL_FreeSurface_soloader},
	{"SDL_GetError", (uintptr_t)&SDL_GetError},
	{"SDL_IntersectRect", (uintptr_t)&SDL_IntersectRect},
	{"SDL_LogError", (uintptr_t)&SDL_LogError},
	{"SDL_MapRGB", (uintptr_t)&SDL_MapRGB},
	{"SDL_free", (uintptr_t)&SDL_free},
	{"SDL_malloc", (uintptr_t)&SDL_malloc},
	{"SDL_CreateMutex", (uintptr_t)&SDL_CreateMutex},
	{"SDL_CreateTexture", (uintptr_t)&SDL_CreateTexture},
	{"SDL_DestroyMutex", (uintptr_t)&SDL_DestroyMutex},
	{"SDL_DestroyTexture", (uintptr_t)&SDL_DestroyTexture},
	{"SDL_LockMutex", (uintptr_t)&SDL_LockMutex},
	{"SDL_RenderCopy", (uintptr_t)&SDL_RenderCopy_soloader},
	{"SDL_RenderPresent", (uintptr_t)&SDL_RenderPresent_soloader},
	{"SDL_UnlockMutex", (uintptr_t)&SDL_UnlockMutex},
	{"SDL_UpdateTexture", (uintptr_t)&SDL_UpdateTexture_soloader},
	{"SDL_Delay", (uintptr_t)&SDL_Delay},
	{"SDL_GetTicks", (uintptr_t)&SDL_GetTicks},
	{"SDL_RWFromMem", (uintptr_t)&SDL_RWFromMem},
	{"SDL_RWclose", (uintptr_t)&SDL_RWclose},
	{"SDL_GetKeyboardState", (uintptr_t)&SDL_GetKeyboardState},
	{"SDL_AllocRW", (uintptr_t)&SDL_AllocRW},
	{"SDL_AudioInit", (uintptr_t)&SDL_AudioInit},
	{"SDL_AudioQuit", (uintptr_t)&SDL_AudioQuit},
	{"SDL_CreateRGBSurface", (uintptr_t)&SDL_CreateRGBSurface},
	{"SDL_CreateRGBSurfaceWithFormat", (uintptr_t)&SDL_CreateRGBSurfaceWithFormat},
	{"SDL_CreateRenderer", (uintptr_t)&SDL_CreateRenderer_soloader},
	{"SDL_CreateTextureFromSurface", (uintptr_t)&SDL_CreateTextureFromSurface_soloader},
	{"SDL_CreateWindow", (uintptr_t)&SDL_CreateWindow_soloader},
	{"SDL_DestroyRenderer", (uintptr_t)&SDL_DestroyRenderer},
	{"SDL_DestroyWindow", (uintptr_t)&SDL_DestroyWindow},
	{"SDL_Error", (uintptr_t)&SDL_Error},
	{"SDL_FreeRW", (uintptr_t)&SDL_FreeRW},
	{"SDL_GL_SetAttribute", (uintptr_t)&SDL_GL_SetAttribute},
	{"SDL_GameControllerClose", (uintptr_t)&SDL_GameControllerClose},
	{"SDL_GameControllerOpen", (uintptr_t)&SDL_GameControllerOpen},
	{"SDL_GetAudioDriver", (uintptr_t)&SDL_GetAudioDriver},
	{"SDL_GetCurrentAudioDriver", (uintptr_t)&SDL_GetCurrentAudioDriver},
	{"SDL_GetCurrentDisplayMode", (uintptr_t)&SDL_GetCurrentDisplayMode},
	{"SDL_GetCurrentVideoDriver", (uintptr_t)&SDL_GetCurrentVideoDriver},
	{"SDL_GetDesktopDisplayMode", (uintptr_t)&SDL_GetDesktopDisplayMode},
	{"SDL_GetDisplayBounds", (uintptr_t)&SDL_GetDisplayBounds},
	{"SDL_GetDisplayMode", (uintptr_t)&SDL_GetDisplayMode},
	{"SDL_GetDisplayName", (uintptr_t)&SDL_GetDisplayName},
	{"SDL_GetKeyName", (uintptr_t)&SDL_GetKeyName},
	{"SDL_GetNumAudioDrivers", (uintptr_t)&SDL_GetNumAudioDrivers},
	{"SDL_GetNumDisplayModes", (uintptr_t)&SDL_GetNumDisplayModes},
	{"SDL_GetNumRenderDrivers", (uintptr_t)&SDL_GetNumRenderDrivers},
	{"SDL_GetNumVideoDisplays", (uintptr_t)&SDL_GetNumVideoDisplays},
	{"SDL_GetNumVideoDrivers", (uintptr_t)&SDL_GetNumVideoDrivers},
	{"SDL_GetPixelFormatName", (uintptr_t)&SDL_GetPixelFormatName},
	{"SDL_GetRenderDriverInfo", (uintptr_t)&SDL_GetRenderDriverInfo},
	{"SDL_GetRendererInfo", (uintptr_t)&SDL_GetRendererInfo},
	{"SDL_GetVideoDriver", (uintptr_t)&SDL_GetVideoDriver},
	{"SDL_GetWindowFromID", (uintptr_t)&SDL_GetWindowFromID},
	{"SDL_GetWindowSize", (uintptr_t)&SDL_GetWindowSize},
	{"SDL_InitSubSystem", (uintptr_t)&SDL_InitSubSystem},
	{"SDL_IsGameController", (uintptr_t)&SDL_IsGameController},
	{"SDL_JoystickClose", (uintptr_t)&SDL_JoystickClose},
	{"SDL_JoystickEventState", (uintptr_t)&SDL_JoystickEventState},
	{"SDL_JoystickGetGUID", (uintptr_t)&SDL_JoystickGetGUID},
	{"SDL_JoystickGetGUIDString", (uintptr_t)&SDL_JoystickGetGUIDString},
	{"SDL_JoystickInstanceID", (uintptr_t)&SDL_JoystickInstanceID},
	{"SDL_JoystickNameForIndex", (uintptr_t)&SDL_JoystickNameForIndex},
	{"SDL_JoystickNumAxes", (uintptr_t)&SDL_JoystickNumAxes},
	{"SDL_JoystickNumBalls", (uintptr_t)&SDL_JoystickNumBalls},
	{"SDL_JoystickNumButtons", (uintptr_t)&SDL_JoystickNumButtons},
	{"SDL_JoystickNumHats", (uintptr_t)&SDL_JoystickNumHats},
	{"SDL_JoystickOpen", (uintptr_t)&SDL_JoystickOpen},
	{"SDL_LoadBMP_RW", (uintptr_t)&SDL_LoadBMP_RW},
	{"SDL_LogSetPriority", (uintptr_t)&SDL_LogSetPriority},
	{"SDL_NumJoysticks", (uintptr_t)&SDL_NumJoysticks},
	{"SDL_PixelFormatEnumToMasks", (uintptr_t)&SDL_PixelFormatEnumToMasks},
	{"SDL_Quit", (uintptr_t)&SDL_Quit},
	{"SDL_RWFromFile", (uintptr_t)&SDL_RWFromFile},
	{"SDL_RenderClear", (uintptr_t)&SDL_RenderClear},
	{"SDL_RenderSetLogicalSize", (uintptr_t)&SDL_RenderSetLogicalSize},
	{"SDL_RenderSetScale", (uintptr_t)&SDL_RenderSetScale},
	{"SDL_SetError", (uintptr_t)&SDL_SetError},
	{"SDL_SetHint", (uintptr_t)&SDL_SetHint},
	{"SDL_SetRenderDrawColor", (uintptr_t)&SDL_SetRenderDrawColor},
	{"SDL_SetWindowDisplayMode", (uintptr_t)&SDL_SetWindowDisplayMode},
	{"SDL_SetWindowFullscreen", (uintptr_t)&SDL_SetWindowFullscreen},
	{"SDL_SetWindowMaximumSize", (uintptr_t)&SDL_SetWindowMaximumSize},
	{"SDL_SetWindowMinimumSize", (uintptr_t)&SDL_SetWindowMinimumSize},
	{"SDL_ShowWindow", (uintptr_t)&SDL_ShowWindow},
	{"SDL_UpperBlitScaled", (uintptr_t)&SDL_UpperBlitScaled_soloader},
	{"SDL_VideoInit", (uintptr_t)&SDL_VideoInit},
	{"SDL_memset", (uintptr_t)&SDL_memset},
	{"SDL_snprintf", (uintptr_t)&SDL_snprintf},
	{"SDL_strcasecmp", (uintptr_t)&SDL_strcasecmp},
	{"SDL_strlcpy", (uintptr_t)&SDL_strlcpy},
	{"SDL_PollEvent", (uintptr_t)&SDL_PollEvent_soloader},
	{"SDL_memcpy", (uintptr_t)&SDL_memcpy},
	{"SDL_strlen", (uintptr_t)&SDL_strlen},
	{"SDL_HasIntersection", (uintptr_t)&SDL_HasIntersection},
	{"SDL_SetTextureAlphaMod", (uintptr_t)&SDL_SetTextureAlphaMod},
	{"SDL_SetTextureColorMod", (uintptr_t)&SDL_SetTextureColorMod},
	{"SDL_AtomicAdd", (uintptr_t)&SDL_AtomicAdd},

	// SDL_ttf
	{"TTF_CloseFont", (uintptr_t)&TTF_CloseFont_soloader},
	{"TTF_OpenFontRW", (uintptr_t)&TTF_OpenFontRW_soloader},
	{"TTF_RenderUTF8_Blended", (uintptr_t)&TTF_RenderUTF8_Blended_soloader},
	{"TTF_RenderUTF8_Shaded", (uintptr_t)&TTF_RenderUTF8_Shaded_soloader},
	{"TTF_RenderUTF8_Solid", (uintptr_t)&TTF_RenderUTF8_Solid_soloader},
	{"TTF_Init", (uintptr_t)&TTF_Init},
	{"TTF_Quit", (uintptr_t)&TTF_Quit},

	// SDL_mix
	{"Mix_HookMusic", (uintptr_t)&Mix_HookMusic},
	{"Mix_QuerySpec", (uintptr_t)&Mix_QuerySpec},
	{"Mix_FreeMusic", (uintptr_t)&Mix_FreeMusic},
	{"Mix_HaltMusic", (uintptr_t)&Mix_HaltMusic},
	{"Mix_HookMusicFinished", (uintptr_t)&Mix_HookMusicFinished},
	{"Mix_LoadMUS_RW", (uintptr_t)&Mix_LoadMUS_RW},
	{"Mix_Pause", (uintptr_t)&Mix_Pause},
	{"Mix_PauseMusic", (uintptr_t)&Mix_PauseMusic},
	{"Mix_PlayMusic", (uintptr_t)&Mix_PlayMusic},
	{"Mix_Resume", (uintptr_t)&Mix_Resume},
	{"Mix_ResumeMusic", (uintptr_t)&Mix_ResumeMusic},
	{"Mix_VolumeMusic", (uintptr_t)&Mix_VolumeMusic},
	{"Mix_AllocateChannels", (uintptr_t)&Mix_AllocateChannels},
	{"Mix_ChannelFinished", (uintptr_t)&Mix_ChannelFinished},
	{"Mix_FreeChunk", (uintptr_t)&Mix_FreeChunk},
	{"Mix_HaltChannel", (uintptr_t)&Mix_HaltChannel},
	{"Mix_LoadWAV_RW", (uintptr_t)&Mix_LoadWAV_RW},
	{"Mix_PlayChannelTimed", (uintptr_t)&Mix_PlayChannelTimed},
	{"Mix_SetPanning", (uintptr_t)&Mix_SetPanning},
	{"Mix_Volume", (uintptr_t)&Mix_Volume},
	{"Mix_Init", (uintptr_t)&Mix_Init},
	{"Mix_OpenAudio", (uintptr_t)&Mix_OpenAudio},
	{"Mix_Quit", (uintptr_t)&Mix_Quit},

	// SDL_img
	{"IMG_Load_RW", (uintptr_t)&IMG_Load_RW},

	// smpeg2
	{"SMPEG_actualSpec", (uintptr_t)&SMPEG_actualSpec},
	{"SMPEG_delete", (uintptr_t)&SMPEG_delete},
	{"SMPEG_enableaudio", (uintptr_t)&SMPEG_enableaudio},
	{"SMPEG_enablevideo", (uintptr_t)&SMPEG_enablevideo},
	{"SMPEG_new_rwops", (uintptr_t)&SMPEG_new_rwops_soloader},
	{"SMPEG_pause", (uintptr_t)&SMPEG_pause},
	{"SMPEG_play", (uintptr_t)&SMPEG_play},
	{"SMPEG_playAudioSDL", (uintptr_t)&SMPEG_playAudioSDL},
	{"SMPEG_setdisplay", (uintptr_t)&SMPEG_setdisplay},
	{"SMPEG_setvolume", (uintptr_t)&SMPEG_setvolume},
	{"SMPEG_status", (uintptr_t)&SMPEG_status},
	{"SMPEG_stop", (uintptr_t)&SMPEG_stop},
};
#define N_DYNLIB_FUNCTIONS sizeof(dynlib_functions) / sizeof(SoDefaultDynlib)

int getNumberOfDynlibFunctions()
{
	return N_DYNLIB_FUNCTIONS;
}